﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ArgusPCLib.Threading.Collections;

namespace ArgusPCLib.Threading.Tasks
{
	public class BackgroundWorker : IDisposable
	{
		#region Static
		public static Task StartTasks(IEnumerable<BackgroundWorkerTask> tasks)
		{
			return BackgroundWorker.StartTasks(tasks, new StartTasksSettings());
		}

		public static Task StartTasks(IEnumerable<BackgroundWorkerTask> tasks, StartTasksSettings settings)
		{
			ISynchronizedStack<BackgroundWorkerTask> syncedTasks = new SynchronizedQueue<BackgroundWorkerTask>(tasks);
			return BackgroundWorker.StartTasks(syncedTasks, settings);
		}

		static Task StartTasks(ISynchronizedStack<BackgroundWorkerTask> tasks, StartTasksSettings settings)
		{
			return Task.Factory.StartNew(() =>
				{
					if (settings == null)
						settings = new StartTasksSettings();

					SynchronizedInt32 count = new SynchronizedInt32(0);
					SynchronizedDictionaryWrapper<object, Task> workerTasks = new SynchronizedDictionaryWrapper<object, Task>(new Dictionary<object, Task>());
					while (workerTasks.Count < settings.NumberOfThreads)
						workerTasks.Add(BackgroundWorker.GetWorkerTask(workerTasks, tasks, settings,count));

					while (!settings.ThreadState.CancelRequested)
					{
						Task.WaitAll(workerTasks.Values.ToArray());

						if (workerTasks.Count < 1 || settings.ThreadState.CancelRequested)
							break;
					}
				});
		}

		static KeyValuePair<object,Task> GetWorkerTask(SynchronizedDictionaryWrapper<object, Task> workerTasks, ISynchronizedStack<BackgroundWorkerTask> tasks, StartTasksSettings settings, SynchronizedInt32 count)
		{
			object key = new object();
			return new KeyValuePair<object, Task>(key, Task.Factory.StartNew(() =>
				{
					while (tasks.Count > 0 && !settings.ThreadState.CancelRequested)
					{
						BackgroundWorkerTask bgWorkerTask;
						if (tasks.TryPop(out bgWorkerTask) == false)
							break;

						IEnumerable<BackgroundWorkerTask> newTasks = bgWorkerTask.TaskAction(settings.ThreadState, bgWorkerTask.AsyncState);
						if (newTasks != null)
							tasks.PushRange(newTasks);
						count.Increment();
						settings.RaiseTaskCompleted(null, new TaskCompletedEventArgs(count.Object, tasks.Count, bgWorkerTask.AsyncState));

						if (workerTasks.Count < settings.NumberOfThreads && tasks.Count > 0)
						{
							int n = settings.NumberOfThreads - workerTasks.Count;
							for (int i = 0; i < n; i++)
								workerTasks.Add(BackgroundWorker.GetWorkerTask(workerTasks, tasks, settings, count));
						}
						settings.ThreadState.WaitForResumption();
					}
					workerTasks.Remove(key);
				}));
		}
		#endregion

		ISynchronizedStack<BackgroundWorkerTask> tasks;
		SynchronizedObject<BackgroundWorkerStates> _state;

		public StartTasksSettings StartTasksSettings { get; private set; }
		public BackgroundWorkerStates State
		{
			get { return this._state.Object; }
			private set
			{
				if (this._state.Object == value)
					return;
				this._state.Object = value;
				this.OnStateChanged(EventArgs.Empty);
			}
		}

		public event TaskCompletedEventHandler TaskCompleted
		{
			add { this.StartTasksSettings.TaskCompleted += value; }
			remove { this.StartTasksSettings.TaskCompleted -= value; }
		}

		public event EventHandler StateChanged;

		public BackgroundWorker()
			: this(null, null) { }

		public BackgroundWorker(StartTasksSettings settings)
			: this(settings, null) { }

		public BackgroundWorker(IEnumerable<BackgroundWorkerTask> tasks)
			: this(null, tasks) { }

		public BackgroundWorker(StartTasksSettings settings, IEnumerable<BackgroundWorkerTask> tasks)
		{
			if (settings == null)
				this.StartTasksSettings = new StartTasksSettings();
			else
				this.StartTasksSettings = settings;

			this._state = new SynchronizedObject<BackgroundWorkerStates>(BackgroundWorkerStates.Stopped);

			if (tasks != null)
				this.tasks = new SynchronizedQueue<BackgroundWorkerTask>(tasks);
			else
				this.tasks = new SynchronizedQueue<BackgroundWorkerTask>();
		}

		public void AddTasks(IEnumerable<BackgroundWorkerTask> tasks)
		{
			this.tasks.PushRange(tasks);
			if (this.State == BackgroundWorkerStates.Idle)
				this.Start();
		}

		public void AddTask(BackgroundWorkerTask task)
		{
			this.tasks.Push(task);
			if (this.State == BackgroundWorkerStates.Idle)
				this.Start();
		}

		public void Start()
		{
			if (this.State == BackgroundWorkerStates.Running)
				return;

			if (this.tasks.Count < 1)
			{
				this.State = BackgroundWorkerStates.Idle;
				return;
			}

			this.State = BackgroundWorkerStates.Running;
			this.StartTasksSettings.ThreadState.ResetCancelRequest();
			BackgroundWorker.StartTasks(this.tasks, this.StartTasksSettings).ContinueWith(new Action<Task>((t) =>
				{
					if (this.StartTasksSettings.ThreadState.CancelRequested)
						this.State = BackgroundWorkerStates.Stopped;
					else
						this.State = BackgroundWorkerStates.Idle;
				}));
		}

		public void Stop()
		{
			this.StartTasksSettings.ThreadState.RequestCancel();
			if (this.State == BackgroundWorkerStates.Idle)
				this.State = BackgroundWorkerStates.Stopped;
		}

		void OnStateChanged(EventArgs e)
		{
			if (this.StateChanged != null)
				this.StateChanged(this, e);
		}

		void IDisposable.Dispose()
		{
			this.StartTasksSettings.ThreadState.RequestCancel();
			this.tasks = null;
			this._state = null;
		}
	}
}
